{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Tce3stUlHN0L"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "tuOe1ymfHZPu"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qFdPvlXBOdUN"
      },
      "source": [
        "# 혼합 정밀도"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/mixed_precision\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/guide/mixed_precision.ipynb\" class=\"\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/guide/mixed_precision.ipynb\" class=\"\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub 소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/guide/mixed_precision.ipynb\" class=\"\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\"> 노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xHxb-dlhMIzW"
      },
      "source": [
        "## 개요\n",
        "\n",
        "혼합 정밀도는 훈련 중에 모델에서 16-bit 및 32-bit 부동 소수점 유형을 모두 사용하여 더 빠르게 실행하고 메모리를 적게 사용하는 것입니다. 수치 안정성을 위해 모델의 특정 부분을 32-bit 유형으로 유지함으로써 모델은 정확도와 같은 평가 메트릭스 측면에서 더 낮은 스텝 타임을 가지며 동일하게 훈련합니다. 이 가이드는 실험적인 Keras 혼합 정밀도 API를 사용하여 모델 속도를 높이는 방법을 설명합니다. 이 API를 사용하면 최신 GPU에서 3배 이상, TPU에서 60% 이상의 성능을 향상할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AEDHaeuR7kLX"
      },
      "source": [
        "참고: Keras 혼합 정밀도 API는 현재 실험 중이며 변경 될 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3vsYi_bv7gS_"
      },
      "source": [
        "오늘날 대부분의 모델은 32-bit 메모리를 사용하는 float32 dtype을 사용합니다. 그러나 정밀도가 낮은 dtype인 float16과 bfloat16도 있으며 각각은 16-bit의 메모리를 사용합니다. 최신 가속기는 16-bit 계산을 실행할 수 있는 특수한 하드웨어가 있어 16-bit dtype을 메모리에서 더 빨리 읽을 수 있으므로 16-bit dtype에서 연산을 더 빠르게 실행할 수 있습니다.\n",
        "\n",
        "NVIDIA GPU는 float32보다 float16에서 연산을 더 빠르게 실행할 수 있고 TPU는 float32보다 bfloat16에서 연산을 더 빨리 실행할 수 있습니다. 따라서 이러한 정밀도가 낮은 dtype은 해당 장치에서 가능할 때마다 사용해야 합니다. 그러나 변수와 몇 가지 계산은 여전히 숫자로 인해 float32에 있어야 모델이 동일한 품질로 훈련합니다. Keras 혼합 정밀도 API를 사용하면 float16 또는 bfloat16을 float32와 함께 사용하여 float16/bfloat16의 성능 이점과 float32의 수치 안정성 이점을 얻을 수 있습니다.\n",
        "\n",
        "참고: 이 가이드에서 \"수치 안정성\"이라는 용어는 고정밀 dtype 대신 저정밀 dtype의 사용이 모델의 품질에 어떤 영향을 미치는지를 나타냅니다. float16 또는 bfloat16에서 dtype 중 하나의 실행이 float32에서 연산을 실행하는 것과 비교하여 모델의 평가 정확도 또는 기타 메트릭이 악화하는 경우 연산이 \"수치상으로 불안정\"하다고 말합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MUXex9ctTuDB"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1Eh-iCRVBm0p"
      },
      "source": [
        "Keras 혼합 정밀도 API는 TensorFlow 2.1에서 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IqR2PQG4ZaZ0"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers\n",
        "from tensorflow.keras.mixed_precision import experimental as mixed_precision"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "814VXqdh8Q0r"
      },
      "source": [
        "## 지원하는 하드웨어\n",
        "\n",
        "혼합 정밀도는 대부분의 하드웨어에서 실행되지만 최신 NVIDIA GPU 및 Cloud TPU에서는 모델의 속도만 향상됩니다. NVIDIA GPU는 float16과 float32의 혼합 사용을 지원하는 반면 TPU는 bfloat16과 float32의 혼합을 지원합니다.\n",
        "\n",
        "NVIDIA GPU 중에서 컴퓨팅 능력이 7.0 이상인 유닛은 float16 행렬 곱셈 및 컨볼루션을 가속하기 위한 Tensor Cores라는 특수 하드웨어 장치가 있기 때문에 혼합 정밀도의 최대 성능 이점을 볼 수 있습니다. 구형 GPU는 혼합 정밀도 사용에 대한 수학 성능 이점을 제공하지 않지만, 메모리 및 대역폭 절약은 일부 속도 향상을 가능하게 합니다. NVIDIA의 [CUDA GPU 웹 페이지](https://developer.nvidia.com/cuda-gpus)에서 GPU의 컴퓨팅 기능을 찾아볼 수 있습니다. 혼합 정밀도의 이점이 가장 큰 GPU의 예로는 RTX GPU, Titan V 및 V100이 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-q2hisD60F0_"
      },
      "source": [
        "참고: Google Colab에서 이 가이드를 실행하면 일반적으로 GPU 런타임에 P100이 연결됩니다. P100은 컴퓨팅 능력 6.0을 갖추고 있으며 눈에띄는 속도 향상을 기대하지는 않습니다.\n",
        "\n",
        "다음을 통해 GPU 타입을 확인할 수 있습니다. 이 명령은 NVIDIA 드라이버가 설치된 경우에만 존재하므로 다음과 같은 경우 오류가 발생합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "j-Yzg_lfkoa_"
      },
      "outputs": [],
      "source": [
        "!nvidia-smi -L"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hu_pvZDN0El3"
      },
      "source": [
        "모든 Cloud TPU는 bfloat16을 지원합니다.\n",
        "\n",
        "속도 향상이 예상되지 않는 CPU 및 구형 GPU에서도 여전히 혼합 정밀도 API를 유닛 테스트, 디버깅 또는 API 테스트에 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HNOmvumB-orT"
      },
      "source": [
        "## dtype 정책 설정"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "54ecYY2Hn16E"
      },
      "source": [
        "Keras에서 혼합 정밀도를 사용하려면 일반적으로 *dtype 정책*이라는  `tf.keras.mixed_precision.experimental.Policy`를 생성해야합니다. Dtype 정책은 레이어가 실행될 레이어를 지정합니다. 이 가이드에서는 `'mixed_float16'` 문자열로 정책을 구성하고 이를 전역 정책으로 설정합니다. 이로 인해 이후에 생성된 레이어가 float16과 float32를 혼합하여 혼합 정밀도를 사용합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "x3kElPVH-siO"
      },
      "outputs": [],
      "source": [
        "policy = mixed_precision.Policy('mixed_float16')\n",
        "mixed_precision.set_policy(policy)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oGAMaa0Ho3yk"
      },
      "source": [
        "이 정책은 레이어의 두 가지 중요한 측면, 즉 레이어 계산이 수행되는 dtype과 레이어 변수의 dtype을 지정합니다. 위에서 `mixed_float16` 정책을 만들었습니다(예, 문자열 `'mixed_float16'`을 생성자에 전달하여 만든 `mixed_precision.Policy`). 이 정책에서 레이어는 float16 계산 및 float32 변수를 사용합니다. 계산은 성능을 위해 float16에서 수행되지만, 수치 안정성을 위해서는 변수를 float32로 유지해야 합니다. 이러한 정책 속성을 직접 쿼리 할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "GQRbYm4f8p-k"
      },
      "outputs": [],
      "source": [
        "print('Compute dtype: %s' % policy.compute_dtype)\n",
        "print('Variable dtype: %s' % policy.variable_dtype)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MOFEcna28o4T"
      },
      "source": [
        "앞에서 언급했듯이 `mixed_float16` 정책은 7.0 이상의 컴퓨팅 능력을 갖춘 NVIDIA GPU의 성능이 가장 크게 향상됩니다. 이 정책은 다른 GPU 및 CPU에서 실행되지만, 성능이 향상되지 않을 수 있습니다. TPU의 경우 `mixed_bfloat16` 정책을 대신 사용해야 합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cAHpt128tVpK"
      },
      "source": [
        "## 모델 구축"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nB6ujaR8qMAy"
      },
      "source": [
        "다음으로 간단한 모델을 만들어 봅시다. TensorFlow 런타임의 오버헤드가 일반적으로 실행 시간을 지배하여 GPU의 성능 향상을 무시할 수 있기 때문에 매우 작은 토이 모델은 일반적으로 혼합 정밀도의 이점을 얻지 못합니다. 따라서 GPU를 사용하는 경우 각각 4096개의 유닛으로 두 개의 큰 `Dense` 레이어를 만들어 봅시다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0DQM24hL_14Q"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "if tf.config.list_physical_devices('GPU'):\n",
        "  print('The model will run with 4096 units on a GPU')\n",
        "  num_units = 4096\n",
        "else:\n",
        "  # Use fewer units on CPUs so the model finishes in a reasonable amount of time\n",
        "  print('The model will run with 64 units on a CPU')\n",
        "  num_units = 64\n",
        "dense1 = layers.Dense(num_units, activation='relu', name='dense_1')\n",
        "x = dense1(inputs)\n",
        "dense2 = layers.Dense(num_units, activation='relu', name='dense_2')\n",
        "x = dense2(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2dezdcqnOXHk"
      },
      "source": [
        "각 계층에는 정책이 있으며 기본적으로 전역 정책을 사용합니다. 따라서 이전에 전역 정책을 `mixed_float16`으로 설정했기 때문에 각 `Dense` 레이어는 `mixed_float16` 정책을 갖습니다. 이로 인해 밀도가 높은 레이어는 float16 계산을 수행하고 float32 변수를 갖게됩니다. float16 계산을 수행하기 위해 입력을 float16으로 캐스팅하여 결과적으로 출력이 float16이됩니다. 변수는 float32이며 dtype 불일치로 인한 오류를 피하기 위해 레이어를 호출하면 float16으로 캐스팅됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kC58MzP4PEcC"
      },
      "outputs": [],
      "source": [
        "print('x.dtype: %s' % x.dtype.name)\n",
        "# 'kernel' is dense1's variable\n",
        "print('dense1.kernel.dtype: %s' % dense1.kernel.dtype.name)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_WAZeqDyqZcb"
      },
      "source": [
        "다음으로 출력 예측을 작성합니다. 일반적으로 다음과 같이 출력 예측을 작성할 수 있지만 float16에서는 항상 수치상으로 안정적이지는 않습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ybBq1JDwNIbz"
      },
      "outputs": [],
      "source": [
        "# INCORRECT: softmax and model output will be float16, when it should be float32\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "print('Outputs dtype: %s' % outputs.dtype.name)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "D0gSWxc9NN7q"
      },
      "source": [
        "모델의 끝에서 softmax 활성화는 float32이어야 합니다. dtype 정책이 `mixed_float16`이므로, softmax 활성화는 일반적으로 float16 계산 dtype을 가지며 float16 텐서를 출력합니다.\n",
        "\n",
        "Dense와 softmax 레이어를 분리하고 `dtype='float32'`를 softmax 레이어에 전달하는 것으로 이 문제를 해결할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IGqCGn4BsODw"
      },
      "outputs": [],
      "source": [
        "# CORRECT: softmax and model output are float32\n",
        "x = layers.Dense(10, name='dense_logits')(x)\n",
        "outputs = layers.Activation('softmax', dtype='float32', name='predictions')(x)\n",
        "print('Outputs dtype: %s' % outputs.dtype.name)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tUdkY_DHsP8i"
      },
      "source": [
        "`dtype='float32'`를 softmax 레이어 생성자에 전달하면 레이어의 dtype 정책이 `float32` 정책으로 재정의되어 계산을 수행하고 변수를 float32로 유지합니다. 마찬가지로 `dtype=mixed_precision.Policy('float32')`대신 전달할 수도 있습니다. 레이어는 항상 dtype 인수를 정책으로 변환합니다. `Activation` 레이어에는 변수가 없으므로 정책의 변수 dtype은 무시되지만 float32 정책의 계산 dtype은 softmax 및 모델 출력을 float32로 만듭니다.\n",
        "\n",
        "모델의 중간에 float16 softmax를 추가하는 것은 괜찮지만 모델 끝의 softmax는 float32로 있어야합니다. 그 이유는 softmax에서 손실까지 흐르는 중간 텐서가 float16 또는 bfloat16이면 숫자 문제가 발생할 수 있기 때문입니다.\n",
        "\n",
        "float16 계산이 수치상으로 안정적이지 않다고 생각된다면 `dtype='float32'`을 전달하여 모든 레이어의 dtype을 float32로 재정의할 수 있습니다. 하지만 일반적으로 대부분의 레이어는 `mixed_float16` 및 `mixed_bfloat16`으로 충분한 정밀도를 갖기 때문에 모델의 마지막 레이어에서만 필요합니다.\n",
        "\n",
        "모델이 softmax로 끝나지 않더라도 출력은 여전히 float32이어야 합니다. 이 특정 모델에는 필요하지 않지만, 다음을 사용하여 모델 출력을 float32로 캐스팅 할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dzVAoLI56jR8"
      },
      "outputs": [],
      "source": [
        "# The linear activation is an identity function. So this simply casts 'outputs'\n",
        "# to float32. In this particular case, 'outputs' is already float32 so this is a\n",
        "# no-op.\n",
        "outputs = layers.Activation('linear', dtype='float32')(outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tpY4ZP7us5hA"
      },
      "source": [
        "그런 다음 모델을 완료 및 컴파일하고 입력 데이터를 생성합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "g4OT3Z6kqYAL"
      },
      "outputs": [],
      "source": [
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "model.compile(loss='sparse_categorical_crossentropy',\n",
        "              optimizer=keras.optimizers.RMSprop(),\n",
        "              metrics=['accuracy'])\n",
        "\n",
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
        "x_test = x_test.reshape(10000, 784).astype('float32') / 255"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0Sm8FJHegVRN"
      },
      "source": [
        "이 예제는 입력 데이터를 int8에서 float32로 캐스팅합니다. 255로 나누기가 CPU에 있으며 float16 연산은 float32 연산보다 느리기 때문에 float16으로 캐스팅하지 않습니다. 이 경우 성능 차이는 무시할만하지만, 일반적으로 CPU에서 실행되는 경우 float32에서 입력 처리 계산을 실행해야 합니다. 각 레이어는 부동 소수점 입력을 계산 dtype에 캐스팅하므로 모델의 첫 번째 레이어는 입력을 float16으로 캐스팅합니다.\n",
        "\n",
        "모델의 초기 가중치가 검색됩니다. 가중치를 로딩하여 처음부터 다시 훈련 할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0UYs-u_DgiA5"
      },
      "outputs": [],
      "source": [
        "initial_weights = model.get_weights()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zlqz6eVKs9aU"
      },
      "source": [
        "## Model.fit으로 모델 훈련\n",
        "\n",
        "다음으로 모델을 훈련시킵니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "hxI7-0ewmC0A"
      },
      "outputs": [],
      "source": [
        "history = model.fit(x_train, y_train,\n",
        "                    batch_size=8192,\n",
        "                    epochs=5,\n",
        "                    validation_split=0.2)\n",
        "test_scores = model.evaluate(x_test, y_test, verbose=2)\n",
        "print('Test loss:', test_scores[0])\n",
        "print('Test accuracy:', test_scores[1])\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MPhJ9OPWt4x5"
      },
      "source": [
        "모델은 로그에 샘플당 시간을 출력합니다(예: \"4us/sample\"). TensorFlow가 모델을 최적화하는 데 시간을 소비함에 따라 첫 번째 epoch가 느려질 수 있지만 이후 샘플당 시간이 안정화되어야 합니다.\n",
        "\n",
        "Colab에서 이 가이드를 실행하는 경우 혼합 정밀도의 성능과 float32를 비교할 수 있습니다. 그러려면 \"dtype 정책 설정\" 섹션에서 정책을 `mixed_float16`에서 `float32`로 변경한 다음이 시점까지의 모든 셀을 다시 실행해야 합니다. 컴퓨팅 성능이 7.0 이상인 GPU에서는 샘플당 시간이 많이 증가하여 혼합 정밀도가 모델을 가속화 함을 나타냅니다. 예를 들어 Titan V GPU를 사용하면 샘플당 시간이 4us에서 12us로 증가합니다. 가이드를 계속하기 전에 정책을 `mixed_float16`으로 복구하고 셀을 다시 실행합니다.\n",
        "\n",
        "많은 실제 모델의 경우, float16 텐서가 메모리의 절반을 차지하므로 혼합 정밀도를 사용하면 메모리 부족없이 배치 크기를 두 배로 늘릴 수 있습니다. 그러나 각 배치가 60,000개의 이미지로 구성된 전체 MNIST 데이터세트로 구성된 모든 dtype에서 모델을 실행할 수 있으므로, 이 토이 모델에는 적용되지 않습니다.\n",
        "\n",
        "TPU에서 혼합 정밀도를 실행할 경우 GPU에서 혼합 정밀도를 실행하는 것과 비교하여 성능이 크게 향상되지는 않습니다. 이는 기본 dtype 정책이 `float32`인 경우에도 TPU가 이미 bfloat16에서 특정 작업을 수행하기 때문입니다. TPU 하드웨어는 matmul과 같은 bfloat16에서 수치상으로 안정적인 특정 작업을 위해 float32를 지원하지 않습니다. 이러한 작업을 위해 TPU 백엔드는 대신 내부적으로 bfloat16을 암묵적으로 사용합니다. 결과적으로 이러한 작업을 사용하는 레이어에 `dtype='float32'`을 전달하면 수치적 효과가 없을 수 있지만, bfloat16 계산으로 그러한 레이어를 실행하는 것은 거의 불가합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mNKMXlCvHgHb"
      },
      "source": [
        "## 손실 조정\n",
        "\n",
        "손실 조정은 수치 언더플로우를 방지하기 위해 `mixed_float16` 정책으로 `tf.keras.Model.fit`이 자동으로 수행하는 기술입니다. 이 섹션에서는 손실 조정 및 해당 동작을 사용자 정의하는 방법에 대해 설명합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1xQX62t2ow0g"
      },
      "source": [
        "### 언더플로우 및 오버플로우\n",
        "\n",
        "float16 데이터 형식은 float32에 비해 동적 범위가 좁습니다. 이는 $65504$ 이상의 값은 무한대로 오버플로우되고 $6.0 \\times 10^{-8}$ 미만의 값은 0으로 언더플로우됨을 의미합니다. float32 및 bfloat16은 동적 범위가 훨씬 높아 오버플로우 및 언더플로우가 문제가 되지 않습니다.\n",
        "\n",
        "예:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CHmXRb-yRWbE"
      },
      "outputs": [],
      "source": [
        "x = tf.constant(256, dtype='float16')\n",
        "(x ** 2).numpy()  # Overflow"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5unZLhN0RfQM"
      },
      "outputs": [],
      "source": [
        "x = tf.constant(1e-5, dtype='float16')\n",
        "(x ** 2).numpy()  # Underflow"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pUIbhQypRVe_"
      },
      "source": [
        "실제로 float16으로 오버플로우가 거의 발생하지 않습니다. 또한 순방향 전달에서 언더플로우가 거의 발생하지 않습니다. 그러나 역방향 전달동안 그래디언트가 0으로 언더플로우 될 수 있습니다. 손실 조정은 이러한 언더플로우를 방지하는 기술입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FAL5qij_oNqJ"
      },
      "source": [
        "### 손실 조정 배경\n",
        "\n",
        "손실 조정의 기본 개념은 간단합니다. 손실에 어떤 큰 숫자(예: $1024$)를 곱하면 됩니다. 이 숫자를 *손실 규모*라고 부릅니다. 손실 조정은 그래디언트 스케일을 $1024$로 확장하여 언더플로우 가능성을 크게 줄입니다. 최종 그래디언트가 계산되면 $1024$로 나눠서 올바른 값으로 되돌립니다.\n",
        "\n",
        "이 프로세스의 의사 코드는 다음과 같습니다.\n",
        "\n",
        "```\n",
        "loss_scale = 1024 loss = model(inputs) loss *= loss_scale # We assume `grads` are float32. We do not want to divide float16 gradients grads = compute_gradient(loss, model.trainable_variables) grads /= loss_scale\n",
        "```\n",
        "\n",
        "손실 규모를 선택하는 것은 까다로울 수 있습니다. 손실 규모가 너무 낮으면 그래디언트가 여전히 0으로 언더플로우일 수 있습니다. 너무 높으면 반대의 문제가 발생합니다. 그래디언트가 무한대로 오버플로우일 수 있습니다.\n",
        "\n",
        "이를 해결하기 위해 TensorFlow는 손실 스케일을 동적으로 결정하므로 수동으로 선택할 필요가 없습니다. `tf.keras.Model.fit`을 사용하면 손실 스케일링이 수행되므로 추가 작업을 수행할 필요가 없습니다. 이에 대해서는 다음 섹션에서 자세히 설명합니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qlbxbjxShf0m"
      },
      "source": [
        "### 손실 규모 선택\n",
        "\n",
        "각 dtype 정책은 선택적으로 고정 또는 동적 손실 규모를 나타내는 연관 `tf.mixed_precision.experimental.LossScale` 객체를 갖습니다. 기본적으로 `mixed_float16` 정책의 손실 규모는 `tf.mixed_precision.experimental.DynamicLossScale`이며, 이는 동적으로 손실 규모 값을 결정합니다. float16을 사용할때만 필요하므로, 다른 정책들은 기본적으로 손실 규모를 갖지 않습니다. 다음과 같이 정책의 손실 규모를 쿼리할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0iSbassjdldy"
      },
      "outputs": [],
      "source": [
        "loss_scale = policy.loss_scale\n",
        "print('Loss scale: %s' % loss_scale)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "l8c0ULUxdu2b"
      },
      "source": [
        "손실 규모는 많은 내부 상태를 출력하지만 무시해도됩니다. 가장 중요한 부분은 `current_loss_scale` 부분으로, 손실 규모의 현재 값을 보여줍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Mr0cAMCHfhlj"
      },
      "source": [
        "대신 dtype 정책을 구성할 때 숫자를 전달하여 정적 손실 규모를 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4Sa4bGFif9GW"
      },
      "outputs": [],
      "source": [
        "new_policy = mixed_precision.Policy('mixed_float16', loss_scale=1024)\n",
        "print(new_policy.loss_scale)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "alD_0frtgFKK"
      },
      "source": [
        "dtype 정책 생성자는 항상 손실 규모를 `LossScale` 객체로 변환합니다. 이 경우 `DynamicLossScale` 이외의 유일한 `LossScale` 서브 클래스 인 `tf.mixed_precision.experimental.FixedLossScale`로 변환됩니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Gi1DLrLF7bnr"
      },
      "source": [
        "참고: *동적 손실 규모 이외의 다른 것의 사용은 권장하지 않습니다*. 고정 손실 규모를 선택하는 것은 어려울 수 있습니다. 너무 낮게하면 모델이 훈련되지 않고 너무 높게 만들면 Infs 또는 NaN이 그래디언트에 나타납니다. 동적 손실 규모는 일반적으로 최적의 손실 규모에 가깝기 때문에 별도의 작업을 수행할 필요가 없습니다. 현재 동적 손실 규모는 고정 손실 규모보다 약간 느리지만 향후 성능이 향상될 것입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LJ_1W-Axh2bp"
      },
      "source": [
        "레이어와 같은 모델에는 각각 dtype 정책이 있습니다. 존재하는 경우, 모델은 정책의 손실 규모를 사용하여 `tf.keras.Model.fit` 메서드에서 손실 조정을 적용합니다. 이는 `Model.fit`을 사용하는 경우 손실 조정에 대해 전혀 걱정할 필요가 없음을 의미합니다. `mixed_float16` 정책은 기본적으로 동적 손실 조정을 가지며 `Model.fit`을 적용합니다.\n",
        "\n",
        "사용자 지정 훈련 루프를 사용하면 모델이 정책의 손실 규모를 무시하므로 수동으로 적용해야 합니다. 이에 대해서는 다음 섹션에서 설명합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yqzbn8Ks9Q98"
      },
      "source": [
        "## 사용자 지정 훈련 루프를 사용하여 모델 훈련"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CRANRZZ69nA7"
      },
      "source": [
        "지금까지 `tf.keras.Model.fit`을 사용하여 혼합 정밀도로 Keras 모델을 훈련했습니다. 다음으로, 사용자 지정 훈련 루프로 혼합 정밀도를 사용할 것입니다. 사용자 지정 훈련 루프가 무엇인지 아직 모르신다면 먼저 [사용자 지정 훈련 가이드](../tutorials/customization/custom_training_walkthrough.ipynb)를 읽어보세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wXTaM8EEyEuo"
      },
      "source": [
        "혼합 정밀도로 사용자 지정 훈련 루프를 실행하려면 float32에서 실행하는것으로부터 두 가지 변경이 필요합니다.\n",
        "\n",
        "1. 혼합 정밀도로 모델을 작성합니다 (이미 수행했습니다)\n",
        "2. `mixed_float16` 을 사용하는 경우 손실 조정을 명시적으로 사용합니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "M2zpp7_65mTZ"
      },
      "source": [
        "2 단계에서는 옵티마이저를 래핑하고 손실 조정을 적용하는 `tf.keras.mixed_precision.experimental.LossScaleOptimizer` 클래스를 사용합니다. 옵티마이저와 손실 조정이라는 두 가지 인수가 필요합니다. 동적 손실 규모를 사용하려면 다음과 같이 구성합니다"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ogZN3rIH0vpj"
      },
      "outputs": [],
      "source": [
        "optimizer = keras.optimizers.RMSprop()\n",
        "optimizer = mixed_precision.LossScaleOptimizer(optimizer, loss_scale='dynamic')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8-_ZZVEC2MZQ"
      },
      "source": [
        "`'dynamic'`을 전달하는 것은 `tf.mixed_precision.experimental.DynamicLossScale()`을 전달하는 것과 같습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JZYEr5hA3MXZ"
      },
      "source": [
        "다음으로 손실 객체와 `tf.data.Dataset`을 정의합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9cE7Mm533hxe"
      },
      "outputs": [],
      "source": [
        "loss_object = tf.keras.losses.SparseCategoricalCrossentropy()\n",
        "train_dataset = (tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "                 .shuffle(10000).batch(8192))\n",
        "test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(8192)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4W0zxrxC3nww"
      },
      "source": [
        "다음으로 훈련 단계 함수를 정의합니다. 손실 규모 옵티마이저의 두 가지 새로운 메서드를 사용하여 손실을 조정하고 그래디언트 조정을 해제합니다.\n",
        "\n",
        "- `get_scaled_loss(loss)`: 손실에 손실 규모를 곱합니다\n",
        "- `get_unscaled_gradients(gradients)`: 조정된 그래디언트 목록을 입력으로 취하고 각각을 손실 규모로 나누어 조정을 해제합니다\n",
        "\n",
        "그래디언트의 언더플로우를 방지하려면 이러한 기능을 사용해야 합니다. `LossScaleOptimizer.apply_gradients`는 Infs 또는 NaN이 없는 그래디언트를 적용합니다. 이는 또한 손실 규모를 업데이트하는데, 그래디언트에 Infs 또는 NaN이 있으면 손실 규모를 절반으로 줄이고 그렇지 않은 경우 잠재적으로 증가시킵니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "V0vHlust4Rug"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def train_step(x, y):\n",
        "  with tf.GradientTape() as tape:\n",
        "    predictions = model(x)\n",
        "    loss = loss_object(y, predictions)\n",
        "    scaled_loss = optimizer.get_scaled_loss(loss)\n",
        "  scaled_gradients = tape.gradient(scaled_loss, model.trainable_variables)\n",
        "  gradients = optimizer.get_unscaled_gradients(scaled_gradients)\n",
        "  optimizer.apply_gradients(zip(gradients, model.trainable_variables))\n",
        "  return loss"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rcFxEjia6YPQ"
      },
      "source": [
        "`LossScaleOptimizer`는 훈련 시작 시 처음 몇 단계를 건너뛸 수 있습니다. 최적의 손실 규모를 신속하게 결정할 수 있도록 손실 규모가 크게 시작됩니다. 몇 단계를 거치면 손실 규모가 안정화되고 몇 단계만 건너뜁니다. 이 프로세스는 자동으로 수행되며 훈련 품질에는 영향을 미치지 않습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IHIvKKhg4Y-G"
      },
      "source": [
        "이제 테스트 단계를 정의합니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nyk_xiZf42Tt"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def test_step(x):\n",
        "  return model(x, training=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hBs98MZyhBOB"
      },
      "source": [
        "모델의 초기 가중치를 로드하여 처음부터 다시 학습할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jpzOe3WEhFUJ"
      },
      "outputs": [],
      "source": [
        "model.set_weights(initial_weights)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s9Pi1ADM47Ud"
      },
      "source": [
        "마지막으로 사용자 지정 훈련 루프를 실행합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "N274tJ3e4_6t"
      },
      "outputs": [],
      "source": [
        "for epoch in range(5):\n",
        "  epoch_loss_avg = tf.keras.metrics.Mean()\n",
        "  test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(\n",
        "      name='test_accuracy')\n",
        "  for x, y in train_dataset:\n",
        "    loss = train_step(x, y)\n",
        "    epoch_loss_avg(loss)\n",
        "  for x, y in test_dataset:\n",
        "    predictions = test_step(x)\n",
        "    test_accuracy.update_state(y, predictions)\n",
        "  print('Epoch {}: loss={}, test accuracy={}'.format(epoch, epoch_loss_avg.result(), test_accuracy.result()))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d7daQKGerOFE"
      },
      "source": [
        "## GPU 성능 팁\n",
        "\n",
        "다음은 GPU에서 혼합 정밀도를 사용할 때의 성능 팁입니다.\n",
        "\n",
        "### 배치 크기 늘리기\n",
        "\n",
        "모델 품질에 영향을 미치지 않으면 혼합 정밀도를 사용할 때 배치 크기를 두 배로 실행합니다. float16 텐서가 절반의 메모리를 사용하므로 메모리 부족없이 배치 크기를 두 배로 늘릴 수 있습니다. 배치 크기를 늘리면 일반적으로 훈련 처리량, 즉 모델을 실행할 수 있는 초당 훈련 요소가 증가합니다.\n",
        "\n",
        "### GPU Tensor Cores 사용 보장\n",
        "\n",
        "앞에서 언급했듯이 최신 NVIDIA GPU는 float16 행렬을 매우 빠르게 곱할 수 있는 Tensor Cores라는 특수 하드웨어 장치를 사용합니다. 그러나 Tensor Cores는 텐서의 특정 크기가 8의 배수 이어야 합니다. 다음 예에서는 텐서 코어를 사용하기 위해 8의 배수 이어야하는 경우에만 인수가 굵게 표시됩니다.\n",
        "\n",
        "- tf.keras.layers.Dense(**units=64**)\n",
        "- tf.keras.layers.Conv2d(**filters=48**, kernel_size=7, stride=3)\n",
        "    - tf.keras.layers.Conv3d와 같은 다른 컨볼루션 레이어와 유사합니다\n",
        "- tf.keras.layers.LSTM(**units=64**)\n",
        "    - tf.keras.layers.GRU와 같은 다른 RNN과 유사합니다\n",
        "- tf.keras.Model.fit(epochs=2, **batch_size=128**)\n",
        "\n",
        "가능하면 Tensor Cores를 사용합니다. 자세한 내용을 보려면 [NVIDIA 딥 러닝 성능 가이드](https://docs.nvidia.com/deeplearning/sdk/dl-performance-guide/index.html)에 Tensor Cores 및 기타 Tensor Cores 관련 성능 정보를 사용하기위한 정확한 요구 사항이 설명되어 있습니다.\n",
        "\n",
        "### XLA\n",
        "\n",
        "XLA는 혼합 정밀도 성능과 float32 성능을 어느 정도 더 향상시킬 수 있는 컴파일러입니다. 자세한 내용은 [XLA 가이드](https://www.tensorflow.org/xla)를 참조하세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2tFDX8fm6o_3"
      },
      "source": [
        "## Cloud TPU 성능 팁\n",
        "\n",
        "bfloat16 텐서는 메모리의 절반을 사용하므로 GPU와 마찬가지로 배치 크기를 두 배로 늘려봅니다. 배치 크기를 두 배로 늘리면 훈련 처리량이 증가할 수 있습니다.\n",
        "\n",
        "TPU는 최적의 성능을 얻기 위해 다른 혼합 정밀도 조정이 필요하지 않습니다. TPU는 이미 XLA의 사용을 필요로합니다. 그들은 특정 크기를 $128$의 배수로 하여 얻는 이점이 있지만, 이는 혼합 정밀도와 마찬가지로 float32에도 동일하게 적용됩니다. float32와 마찬가지로 혼합 정밀도에도 적용되는 일반적인 TPU 성능 팁은 [Cloud TPU 성능 가이드](https://cloud.google.com/tpu/docs/performance-guide)를 참조하세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "--wSEU91wO9w"
      },
      "source": [
        "## 요약\n",
        "\n",
        "- 컴퓨팅 능력이 7.0 이상인 TPU 또는 NVIDIA GPU를 사용하는 경우 성능이 최대 3배 향상되므로 혼합 정밀도를 사용해야 합니다.\n",
        "- 다음과 같이 혼합 정밀도를 사용할 수 있습니다.\n",
        "    ```\n",
        "    # On TPUs, use 'mixed_bfloat16' instead policy = tf.keras.mixed_precision.experimental.Policy('mixed_float16') mixed_precision.set_policy(policy)\n",
        "    ```\n",
        "- 모델이 softmax로 끝나는 경우 float32인지 확인합니다. 모델이 무엇으로 끝나는지에 관계없이 출력이 float32인지 확인합니다.\n",
        "- `mixed_float16` 으로 사용자 지정 훈련 루프를 사용하는 경우 위 코드 외에도 `tf.keras.mixed_precision.experimental.LossScaleOptimizer` 옵티 마이저를 래핑해야합니다. 그런 다음 `optimizer.get_scaled_loss`를 호출하여 손실을 조정하고 `optimizer.get_unscaled_gradients`를 사용하여 그래디언트의 조정을 해제합니다.\n",
        "- 평가 정확도가 떨어지지 않으면 훈련 배치 크기를 두 배로 늘립니다\n",
        "- GPU에서 성능을 최대화하려면 대부분의 텐서 크기가 $8$의 배수가 되도록 합니다\n",
        "\n",
        "`tf.keras.mixed_precision` API를 사용한 혼합 정밀도의 더 많은 예는 [공식 모델 저장소](https://github.com/tensorflow/models/tree/master/official)를 참조하세요. [ResNet](https://github.com/tensorflow/models/tree/master/official/vision/image_classification) 및 [Transformer](https://github.com/tensorflow/models/blob/master/official/nlp/transformer)와 같은 대부분의 공식 모델은 `--dtype=fp16`을 전달하여 혼합 정밀도로 실행됩니다.\n"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "mixed_precision.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
